﻿//------------------------------------------------------------------------------
// Copyright © FRA & FV 2014
// All rights reserved
//------------------------------------------------------------------------------
// Binary Serialization Custom Tool
//
// SVN revision information:
//   $Revision: 92 $
//------------------------------------------------------------------------------
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Win32;
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace FRAFV.Binary.Serialization
{
	public static class BOOL
	{
		public const int FALSE = 0;
		public const int TRUE = 1;
	}

	[Guid("636DE0F4-F6EF-4E0A-AE14-1AE392C5C990")]
	[CustomTool("BinSerializer", "Visual Studio 2012 Custom Tool generates Binary Serialization methods (Read, Write and GetDataSize).")]
	public class BinSerializer : IVsSingleFileGenerator
	{
		const string VisualStudioVersion = "11.0";

		[ComRegisterFunction]
		public static void RegisterClass(Type t)
		{
			GuidAttribute guidAttribute = getGuidAttribute(t);
			CustomToolAttribute customToolAttribute = getCustomToolAttribute(t);
			using (RegistryKey key = Registry.CurrentUser.CreateSubKey(
				ResolveGeneratorsKey(VSConstants.UICONTEXT.CSharpProject_guid, customToolAttribute.Name)))
			{
				key.SetValue("", customToolAttribute.Description);
				key.SetValue("CLSID", "{" + guidAttribute.Value + "}");
				key.SetValue("GeneratesDesignTimeSource", 1);
			}
		}

		[ComUnregisterFunction]
		public static void UnregisterClass(Type t)
		{
			GuidAttribute guidAttribute = getGuidAttribute(t);
			CustomToolAttribute customToolAttribute = getCustomToolAttribute(t);
			Registry.CurrentUser.DeleteSubKey(ResolveGeneratorsKey(
				VSConstants.UICONTEXT.CSharpProject_guid, customToolAttribute.Name), false);
			Registry.CurrentUser.DeleteSubKey(ResolveCLSIDKey(
				guidAttribute.Value), false);
		}

		static GuidAttribute getGuidAttribute(Type t)
		{
			return (GuidAttribute)getAttribute(t, typeof(GuidAttribute));
		}

		static CustomToolAttribute getCustomToolAttribute(Type t)
		{
			return (CustomToolAttribute)getAttribute(t, typeof(CustomToolAttribute));
		}

		static Attribute getAttribute(Type t, Type attributeType)
		{
			object[] attributes = t.GetCustomAttributes(attributeType, /* inherit */ true);
			if (attributes.Length == 0)
				throw new Exception(
				  String.Format("Class '{0}' does not provide a '{1}' attribute.",
				  t.FullName, attributeType.FullName));
			return (Attribute)attributes[0];
		}

		static string ResolveGeneratorsKey(Guid categoryGuid, string toolName)
		{
			return @"SOFTWARE\Microsoft\VisualStudio\" + VisualStudioVersion +
				@"_Config\Generators\{" + categoryGuid + @"}\" + toolName;
		}

		static string ResolveCLSIDKey(string toolGuid)
		{
			return @"SOFTWARE\Microsoft\VisualStudio\" + VisualStudioVersion +
				@"_Config\CLSID\{" + toolGuid + "}";
		}

		public int DefaultExtension(out string pbstrDefaultExtension)
		{
			pbstrDefaultExtension = ".BinSerializer.cs";
			return pbstrDefaultExtension.Length;
		}

		public int Generate(string wszInputFilePath, string bstrInputFileContents,
			string wszDefaultNamespace, System.IntPtr[] rgbOutputFileContents,
			out uint pcbOutput, IVsGeneratorProgress pGenerateProgress)
		{
			var gen = new ClassGenerator(bstrInputFileContents);
			string code = gen.GenerateSerializer(
				(message, row, column) => pGenerateProgress.GeneratorError(BOOL.FALSE, 0u, message, row, column));
			if (code == null)
			{
				pcbOutput = 0;
				return VSConstants.E_FAIL;
			}
			var bytes = Encoding.UTF8.GetBytes(code);
			rgbOutputFileContents[0] = Marshal.AllocCoTaskMem(bytes.Length);
			Marshal.Copy(bytes, 0, rgbOutputFileContents[0], bytes.Length);
			pcbOutput = (uint)bytes.Length;
			return VSConstants.S_OK;
		}
	}
}
